| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508 |
- /* @vitest-environment node */
- import { describe, it, expect, vi, beforeEach } from "vitest";
- vi.mock("@/lib/auth/session", () => ({
- getSession: vi.fn(),
- }));
- vi.mock("@/lib/db", () => ({
- getDb: vi.fn(),
- }));
- vi.mock("@/models/user", () => {
- const USER_ROLES = Object.freeze({
- BRANCH: "branch",
- ADMIN: "admin",
- SUPERADMIN: "superadmin",
- DEV: "dev",
- });
- return {
- default: {
- findById: vi.fn(),
- findOne: vi.fn(),
- findByIdAndDelete: vi.fn(),
- },
- USER_ROLES,
- };
- });
- import { getSession } from "@/lib/auth/session";
- import { getDb } from "@/lib/db";
- import User from "@/models/user";
- import { PATCH, DELETE, dynamic } from "./route.js";
- function createRequestStub(body) {
- return {
- async json() {
- return body;
- },
- };
- }
- describe("PATCH /api/admin/users/[userId]", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- getDb.mockResolvedValue({});
- });
- it('exports dynamic="force-dynamic"', () => {
- expect(dynamic).toBe("force-dynamic");
- });
- it("returns 401 when unauthenticated", async () => {
- getSession.mockResolvedValue(null);
- const res = await PATCH(createRequestStub({}), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(401);
- expect(await res.json()).toEqual({
- error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
- });
- });
- it("returns 403 when authenticated but not allowed (admin)", async () => {
- getSession.mockResolvedValue({
- userId: "u1",
- role: "admin",
- branchId: null,
- email: "admin@example.com",
- });
- const res = await PATCH(createRequestStub({ email: "x@y.de" }), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(403);
- expect(await res.json()).toEqual({
- error: {
- message: "Forbidden",
- code: "AUTH_FORBIDDEN_USER_MANAGEMENT",
- },
- });
- });
- it("returns 400 when JSON parsing fails", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- const req = { json: vi.fn().mockRejectedValue(new Error("invalid json")) };
- const res = await PATCH(req, {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Invalid request body",
- code: "VALIDATION_INVALID_JSON",
- },
- });
- });
- it("returns 400 when body is not an object", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- const res = await PATCH(createRequestStub("nope"), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Invalid request body",
- code: "VALIDATION_INVALID_BODY",
- },
- });
- });
- it("returns 400 when userId param is missing", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- const res = await PATCH(createRequestStub({ email: "x@y.de" }), {
- params: Promise.resolve({ userId: undefined }),
- });
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Missing required route parameter(s)",
- code: "VALIDATION_MISSING_PARAM",
- details: { params: ["userId"] },
- },
- });
- });
- it("returns 400 when userId param is invalid", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- const res = await PATCH(createRequestStub({ email: "x@y.de" }), {
- params: Promise.resolve({ userId: "nope" }),
- });
- expect(res.status).toBe(400);
- expect(await res.json()).toMatchObject({
- error: { code: "VALIDATION_INVALID_FIELD" },
- });
- });
- it("returns 404 when user does not exist", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- User.findById.mockReturnValue({
- exec: vi.fn().mockResolvedValue(null),
- });
- const res = await PATCH(createRequestStub({ email: "x@y.de" }), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(404);
- expect(await res.json()).toEqual({
- error: {
- message: "Not found",
- code: "USER_NOT_FOUND",
- details: { userId: "507f1f77bcf86cd799439011" },
- },
- });
- });
- it("returns 400 when switching to role=branch without branchId (existing has none)", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- const user = {
- _id: "507f1f77bcf86cd799439011",
- username: "x",
- email: "x@example.com",
- role: "admin",
- branchId: null,
- mustChangePassword: false,
- createdAt: new Date(),
- updatedAt: new Date(),
- save: vi.fn(),
- };
- User.findById.mockReturnValue({
- exec: vi.fn().mockResolvedValue(user),
- });
- const res = await PATCH(createRequestStub({ role: "branch" }), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Missing required fields",
- code: "VALIDATION_MISSING_FIELD",
- details: { fields: ["branchId"] },
- },
- });
- });
- it("returns 200 and updates fields; clears branchId for non-branch roles", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- const user = {
- _id: "507f1f77bcf86cd799439011",
- username: "olduser",
- email: "old@example.com",
- role: "branch",
- branchId: "NL01",
- mustChangePassword: true,
- createdAt: new Date("2026-02-01T10:00:00.000Z"),
- updatedAt: new Date("2026-02-02T10:00:00.000Z"),
- save: vi.fn().mockResolvedValue(true),
- };
- User.findById.mockReturnValue({
- exec: vi.fn().mockResolvedValue(user),
- });
- const res = await PATCH(
- createRequestStub({
- role: "admin",
- mustChangePassword: false,
- }),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- },
- );
- expect(res.status).toBe(200);
- expect(user.role).toBe("admin");
- expect(user.branchId).toBe(null);
- expect(user.mustChangePassword).toBe(false);
- expect(user.save).toHaveBeenCalledTimes(1);
- const body = await res.json();
- expect(body).toMatchObject({
- ok: true,
- user: {
- id: "507f1f77bcf86cd799439011",
- username: "olduser",
- email: "old@example.com",
- role: "admin",
- branchId: null,
- mustChangePassword: false,
- },
- });
- });
- it("returns 400 when username is already taken by another user", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- const user = {
- _id: "507f1f77bcf86cd799439011",
- username: "olduser",
- email: "old@example.com",
- role: "admin",
- branchId: null,
- mustChangePassword: false,
- createdAt: new Date(),
- updatedAt: new Date(),
- save: vi.fn(),
- };
- User.findById.mockReturnValue({
- exec: vi.fn().mockResolvedValue(user),
- });
- User.findOne.mockReturnValue({
- select: vi.fn().mockReturnThis(),
- exec: vi.fn().mockResolvedValue({ _id: "507f1f77bcf86cd799439099" }),
- });
- const res = await PATCH(createRequestStub({ username: "TakenUser" }), {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- });
- expect(res.status).toBe(400);
- const body = await res.json();
- expect(body).toEqual({
- error: {
- message: "Username already exists",
- code: "VALIDATION_INVALID_FIELD",
- details: { field: "username", value: "takenuser" },
- },
- });
- });
- });
- describe("DELETE /api/admin/users/[userId]", () => {
- beforeEach(() => {
- vi.clearAllMocks();
- getDb.mockResolvedValue({});
- });
- it("returns 401 when unauthenticated", async () => {
- getSession.mockResolvedValue(null);
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- },
- );
- expect(res.status).toBe(401);
- expect(await res.json()).toEqual({
- error: { message: "Unauthorized", code: "AUTH_UNAUTHENTICATED" },
- });
- });
- it("returns 403 when authenticated but not allowed (admin)", async () => {
- getSession.mockResolvedValue({
- userId: "u1",
- role: "admin",
- branchId: null,
- email: "admin@example.com",
- });
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- },
- );
- expect(res.status).toBe(403);
- expect(await res.json()).toEqual({
- error: {
- message: "Forbidden",
- code: "AUTH_FORBIDDEN_USER_MANAGEMENT",
- },
- });
- expect(User.findByIdAndDelete).not.toHaveBeenCalled();
- });
- it("returns 400 for invalid userId", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "nope" }),
- },
- );
- expect(res.status).toBe(400);
- expect(await res.json()).toMatchObject({
- error: { code: "VALIDATION_INVALID_FIELD" },
- });
- });
- it("returns 400 when trying to delete the current user (self delete)", async () => {
- getSession.mockResolvedValue({
- userId: "507f1f77bcf86cd799439011",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439011" }),
- },
- );
- expect(res.status).toBe(400);
- expect(await res.json()).toEqual({
- error: {
- message: "Cannot delete current user",
- code: "VALIDATION_INVALID_FIELD",
- details: { field: "userId", reason: "SELF_DELETE_FORBIDDEN" },
- },
- });
- expect(User.findByIdAndDelete).not.toHaveBeenCalled();
- });
- it("returns 404 when user does not exist", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "dev",
- branchId: null,
- email: "dev@example.com",
- });
- User.findByIdAndDelete.mockReturnValue({
- select: vi.fn().mockReturnThis(),
- exec: vi.fn().mockResolvedValue(null),
- });
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439099" }),
- },
- );
- expect(res.status).toBe(404);
- expect(await res.json()).toEqual({
- error: {
- message: "Not found",
- code: "USER_NOT_FOUND",
- details: { userId: "507f1f77bcf86cd799439099" },
- },
- });
- });
- it("returns 200 and deleted user payload on success", async () => {
- getSession.mockResolvedValue({
- userId: "u2",
- role: "superadmin",
- branchId: null,
- email: "superadmin@example.com",
- });
- User.findByIdAndDelete.mockReturnValue({
- select: vi.fn().mockReturnThis(),
- exec: vi.fn().mockResolvedValue({
- _id: "507f1f77bcf86cd799439099",
- username: "todelete",
- email: "todelete@example.com",
- role: "branch",
- branchId: "NL01",
- mustChangePassword: true,
- createdAt: new Date("2026-02-01T10:00:00.000Z"),
- updatedAt: new Date("2026-02-02T10:00:00.000Z"),
- }),
- });
- const res = await DELETE(
- new Request("http://localhost/api/admin/users/x"),
- {
- params: Promise.resolve({ userId: "507f1f77bcf86cd799439099" }),
- },
- );
- expect(res.status).toBe(200);
- const body = await res.json();
- expect(body).toMatchObject({
- ok: true,
- user: {
- id: "507f1f77bcf86cd799439099",
- username: "todelete",
- email: "todelete@example.com",
- role: "branch",
- branchId: "NL01",
- mustChangePassword: true,
- },
- });
- });
- });
|